package com.yingxue.demo.util;

import com.yingxue.demo.config.JWTSettings;
import com.yingxue.demo.constant.Constant;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import javax.xml.bind.DatatypeConverter;
import java.time.Duration;
import java.util.Date;
import java.util.Map;

/**
 * @Package: com.company.scaffold.util
 * @Author: Mr.Waves
 * @Date: 2020-03-11 15:00
 * @Description: JWT: JSON Web Token,一种具有指定格式和信息的token
 **/
@Slf4j
public class JWTUtil {
    private static String secretKey;
    private static Duration accessTokenExpireTime;
    private static Duration refreshTokenPCExpireTime;
    private static Duration refreshTokenAppExpireTime;
    private static String issuer;

    public static void setJWTSettings(JWTSettings jwtSettings) {
        secretKey = jwtSettings.getSecretKey();
        accessTokenExpireTime = jwtSettings.getAccessTokenExpireTime();
        refreshTokenPCExpireTime = jwtSettings.getRefreshTokenPCExpireTime();
        refreshTokenAppExpireTime = jwtSettings.getRefreshTokenAppExpireTime();
        issuer = jwtSettings.getIssuer();
    }

    /*
     * 用于生成accessToken
     */
    public static String getAccessToken(String subject, Map<String, Object> claims) {
        return generateToken(issuer,subject, claims, accessTokenExpireTime.toMillis(), secretKey);
    }

    /*
     * 用于生成PC端refreshToken
     */
    public static String getPCRefreshToken(String subject, Map<String, Object> claims) {
        return generateToken(issuer, subject, claims, refreshTokenPCExpireTime.toMillis(), secretKey);
    }

    /*
     * 用于生成APP端refreshToken
     */
    public static String getAPPRefreshToken(String subject, Map<String, Object> claims) {
        return generateToken(issuer, subject, claims, refreshTokenAppExpireTime.toMillis(), secretKey);
    }

    /*
     * 用于生成JWT风格的token
     * issuer: 签发人
     * subject: 主体，代表这个jwt的所有人，一般是用户id
     * claims: 存储在jwt中的信息，一般放些用户的权限/角色信息
     * ttlMillis: 存活时间
     */
    public static String generateToken(String issuer, String subject, Map<String, Object> claims, long ttlMillis, String secretKey) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        byte[] signingKey = DatatypeConverter.parseBase64Binary(secretKey);

        JwtBuilder builder = Jwts.builder();
        if (null != claims) {
            builder.setClaims(claims);
        }
        if (!StringUtils.isEmpty(subject)) {
            builder.setSubject(subject);
        }
        if (!StringUtils.isEmpty(issuer)) {
            builder.setIssuer(issuer);
        }
        builder.setIssuedAt(now);
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date exp = new Date(expMillis);
            builder.setExpiration(exp);
        }
        builder.signWith(signatureAlgorithm, signingKey);
        return builder.compact();  // compact: 签发
    }

    /*
     * 用于解析token，从token中获取数据
     */
    public static Claims getClaimsFromToken(String token) {
        Claims claims = null;
        try {
            claims = Jwts.parser()
                .setSigningKey(DatatypeConverter.parseBase64Binary(secretKey))
                .parseClaimsJws(token)
                .getBody();
        } catch (Exception e) {
            log.error("解析token，获取数据：{}",e.getLocalizedMessage());
        }
        return claims;
    }

    /*
     * 用于获取用户id
     */
    public static String getUserId(String token) {
        String userId = null;
        try {
            Claims claims = getClaimsFromToken(token);
            userId = claims.getSubject();
        } catch (Exception e) {
            log.error("从token中获取用户id：{}",e.getLocalizedMessage());
        }
        return userId;
    }

    /*
     * 用于获取用户名
     */
    public static String getUserName(String token) {
        String userName = null;
        try {
            Claims claims = getClaimsFromToken(token);
            userName = (String) claims.get(Constant.JWT_USER_NAME_KEY);
        } catch (Exception e) {
            log.error("从token中获取用户名：{}",e.getLocalizedMessage());
        }
        return userName;
    }

    /*
     * 用于验证token是否过期
     */
    public static Boolean isTokenExpired(String token) {
        try {
            Claims claims = getClaimsFromToken(token);
            Date expiration = claims.getExpiration();
            return expiration.before(new Date());
        } catch (Exception e) {
            log.error("验证token是否过期：{}",e.getLocalizedMessage());
            return true;
        }
    }

    /*
     * 用于校验token
     */
    public static Boolean validateToken(String token) {
        Claims claims = getClaimsFromToken(token);
        return (null != claims && !isTokenExpired(token));
    }

    /*
     * 用于主动刷新token，改变payload内的信息
     */
    public static String refreshToken(String originalRefreshToken, Map<String, Object> newClaims) {
        String newRefreshToken;
        try {
            Claims originalClaims = getClaimsFromToken(originalRefreshToken);
            // 刷新token的时候如果为空说明原先的用户信息不变，所以就引用原先token里的内容
            if (null == newClaims) {
                newClaims = originalClaims;
            }
            newRefreshToken = generateToken(originalClaims.getIssuer(), originalClaims.getSubject(), newClaims, accessTokenExpireTime.toMillis(), secretKey);
        } catch (Exception e) {
            newRefreshToken = null;
            log.error("刷新token：{}",e.getLocalizedMessage());
        }
        return newRefreshToken;
    }

    /*
     * 用于获取token剩余过期时间
     */
    public static long getRemainingTime(String token) {
        long remainingMillis;
        try {
            long nowMillis = System.currentTimeMillis();
            remainingMillis = getClaimsFromToken(token).getExpiration().getTime() - nowMillis;
        } catch (Exception e) {
            log.error("获取token剩余时间：{}",e.getLocalizedMessage());
            remainingMillis = 0L;
        }
        return remainingMillis;
    }
}
